// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LiveObjectContainerManager_ExternalProcess.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.LiveObjects
{
    #region Using Directives

    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Ipc;
    using System.Runtime.Serialization.Formatters;
    using System.Threading;

    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// Container meant to run inside a separate executable
    /// </summary>
    public class LiveObjectContainerManagerExternalProcess : LiveObjectContainerManager
    {
        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="LiveObjectContainerManagerExternalProcess"/> class.
        /// </summary>
        /// <param name="desc">
        /// The desc.
        /// </param>
        public LiveObjectContainerManagerExternalProcess(ILiveObjectContainerDescriptor desc)
            : base(desc)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets Channel.
        /// </summary>
        private static IChannel Channel { get; set; }

        /// <summary>
        /// Gets or sets Process.
        /// </summary>
        private Process Process { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Create the needed server
        /// </summary>
        /// <param name="thisSideId">
        /// The thisSideId.
        /// </param>
        /// <param name="otherSideId">
        /// The otherSideId.
        /// </param>
        public static void CreateUnsafeCodeLiveObjectContainerServer(string thisSideId, string otherSideId)
        {
            // Channels
            var serverChannel = string.Format("CommandLine\\{0}-{1}", Process.GetCurrentProcess().Id, thisSideId);

            // Server side channel
            var serverProvider = new BinaryServerFormatterSinkProvider();
            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
            var serverProperties = new Hashtable();
            serverProperties["name"] = serverChannel;
            serverProperties["priority"] = "20";
            serverProperties["portName"] = serverChannel;
            var channel2 = new IpcServerChannel(serverProperties, serverProvider);
            ChannelServices.RegisterChannel(channel2, false);

            // Register the object for remote use
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(LiveObjectContainer), thisSideId, WellKnownObjectMode.Singleton);

            // Will trigger and event to "OutputDataReceived" Handler (see client side, above) 
            Stdout.WriteLine("READY " + string.Format("ipc://{0}/{1}", serverChannel, thisSideId));

            // Wait until the container is ready
            while (true)
            {
                Thread.Sleep(1000);
                Stdout.WriteLine("PING");
            }
        }

        /// <summary>
        /// The create container.
        /// </summary>
        /// <returns>
        /// The create container.
        /// </returns>
        public override bool CreateContainer()
        {
            var otctlPath = string.Format("{0}\\OTCtl.{1}", this.Descriptor.WorkingDirectory, this.Descriptor.ID);
            File.Copy("otctlnc.exe", otctlPath);

            // Channels
            var thisSideId = AppDomain.CurrentDomain.FriendlyName.Replace(".exe", string.Empty);
            var otherSideId = this.Descriptor.ID.ToString();
            var serverChannel = string.Format("CommandLine\\{0}-{1}", Process.GetCurrentProcess().Id, thisSideId);

            // Start the UnsafeCode Container 
            var psi = new ProcessStartInfo(otctlPath);

            psi.WorkingDirectory = this.Descriptor.WorkingDirectory;
            psi.Arguments = string.Format("safetyContainer start {0} {1}", otherSideId, thisSideId);
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardInput = true;
            psi.UseShellExecute = false;
            this.Process = Process.Start(psi);
            this.Process.InitializeLifetimeService();

            this.Process.OutputDataReceived += (object sender, DataReceivedEventArgs args) =>
                {
                    // Message written by the process on its output
                    var message = args.Data;

                    if (message == null)
                    {
                        return;
                    }

                    if (message.StartsWith("READY"))
                    {
                        var uri = message.Replace("READY ", string.Empty);

                        // Server side channel
                        if (Channel == null)
                        {
                            var serverProvider = new BinaryServerFormatterSinkProvider();
                            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
                            IDictionary sProperties = new Hashtable();
                            sProperties["name"] = serverChannel;
                            sProperties["priority"] = "20";
                            sProperties["portName"] = serverChannel;
                            Channel = new IpcServerChannel(sProperties, serverProvider);
                            ChannelServices.RegisterChannel(Channel, false);
                        }

                        // Create the remoting stub
                        var containerStub = (MarshalByRefObject)Activator.GetObject(typeof(ILiveObjectContainer), uri);

                        // Stub never dies
                        containerStub.InitializeLifetimeService();

                        // This is the LiveObjectContainer stub 
                        var container = containerStub as LiveObjectContainer;

                        // Initialize
                        container.Initialize(this.Descriptor);

                        // Done !
                        this.Container = container;
                    }
                    else if (message.Equals("PING"))
                    {
                        this.Process.StandardInput.WriteLine("PONG");
                    }
                    else
                    {
                    }
                };

            this.Process.BeginOutputReadLine();

            while (this.Container == null)
            {
                Thread.Sleep(50);
            }

            return true;
        }

        /// <summary>
        /// The destroy container.
        /// </summary>
        /// <returns>
        /// The destroy container.
        /// </returns>
        public override bool DestroyContainer()
        {
            // Call destroy (executed inside the process)
            this.Container.Destroy();

            // Kill the process
            this.Process.Kill();

            // Remove communication channel
            ChannelServices.UnregisterChannel(Channel);

            // Done
            return true;
        }

        #endregion
    }
}

////public class MyIpcServerChannel : IChannelReceiver, IChannel, ISecurableChannel
////{
////    private readonly IpcServerChannel base;
////    public MyIpcServerChannel(System.Collections.IDictionary properties, IServerChannelSinkProvider sinkProvider)

////    {
////        base = new IpcServerChannel(properties, sinkProvider);
////    }

////    #region IChannelReceiver Members

////    public object ChannelData
////    {
////        get { return base.ChannelData; }
////    }

////    public string[] GetUrlsForUri(string objectURI)
////    {
////        return base.GetUrlsForUri(objectURI);
////    }

////    public void StartListening(object data)
////    {
////        base.StartListening(data);
////    }

////    public void StopListening(object data)
////    {
////        base.StopListening(data);
////    }

////    #endregion

////    #region IChannel Members

////    public string ChannelName
////    {
////        get { return base.ChannelName; }
////    }

////    public int ChannelPriority
////    {
////        get { return base.ChannelPriority; }
////    }

////    public string Parse(string url, out string objectURI)
////    {
////        return base.Parse(url, out objectURI);
////    }

////    #endregion

////    #region ISecurableChannel Members

////    public bool IsSecured
////    {
////        get
////        {
////            return base.IsSecured;
////        }
////        set
////        {
////            base.IsSecured = value;
////        }
////    }

////    #endregion
////}

////public class MyIpcClientChannel : IChannelSender, IChannel, ISecurableChannel
////{
////    private readonly IpcClientChannel base;        

////    public MyIpcClientChannel(System.Collections.IDictionary properties, IClientChannelSinkProvider sinkProvider)
////    {
////        base = new IpcClientChannel(properties, sinkProvider);
////    }

////    #region IChannelSender Members

////    public IMessageSink CreateMessageSink(string url, object remoteChannelData, out string objectURI)
////    {
////        return base.CreateMessageSink(url, remoteChannelData, out objectURI);
////    }

////    #endregion

////    #region IChannel Members

////    public string ChannelName
////    {
////        get { return base.ChannelName; }
////    }

////    public int ChannelPriority
////    {
////        get { return base.ChannelPriority; }
////    }

////    public string Parse(string url, out string objectURI)
////    {
////        string r = base.Parse(url, out objectURI);
////        return r;
////    }

////    #endregion

////    #region ISecurableChannel Members

////    public bool IsSecured
////    {
////        get
////        {
////            return base.IsSecured;
////        }
////        set
////        {
////            base.IsSecured = value;
////        }
////    }

////    #endregion
////}